package bbcom.control;

import bbcom.messages.BBCoMModelingException;
import bbcom.messages.BBCoMTraversingException;
import bbcom.modeling.BBCoMModeling;
import bbcom.traversing.BBCoMGlobalTraversing;
import bbcom.traversing.BBCoMLocalTraversing;
import org.apache.commons.cli.*;
import org.apache.commons.io.FileUtils;
import org.apache.xmlbeans.XmlError;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * Created by Thanh-Trung Pham.
 */
public class BBCoMTool {

    private static final String USAGE = "[-h] [-v] [-d] [-l <device_container_name>] <input_model_file>";
    private static final String HEADER = "A tool of BBCoM, Copyright (c) 2016";
    private static final String FOOTER = "For more instructions, refer to our project's website.";

    private static final String COMMAND_ARGS_TEMPLATE_IN_LOCAL = "BBCoMTool.jar -l %s %s";

    private String m_strInputModelFileName;
    private BBCoMModeling m_oBBCoMModeling;

    /**
     * The constructor.
     */
    public BBCoMTool() {

    }


    public static void main(String[] args) {
        BBCoMTool oBBCoMTool = new BBCoMTool();
        oBBCoMTool.doStart(args);
    }

    /**
     * Do start the tool.
     *
     * @param args the arguments.
     */
    private void doStart(String[] args) {
        CommandLineParser oParser = new BasicParser();
        Options oOptions = new Options();
        oOptions.addOption("h", false, "Print the usage information");
        oOptions.addOption("v", false, "Validate the input model");
        oOptions.addOption("d", false, "Turn on the debug mode");
        oOptions.addOption("l", true, "Run on the local device container");

        try {
            CommandLine oCommandLine = oParser.parse(oOptions, args);
            if (oCommandLine.hasOption('h')) {
                printUsageInformation(oOptions);
                return;
            }

            if (oCommandLine.getArgs().length != 1) {
                printInformation("Bad command line arguments were provided!\n", false, true);
                printUsageInformation(oOptions);
                return;
            }

            m_strInputModelFileName = oCommandLine.getArgs()[0];

            boolean isValidationOnly = false;
            if (oCommandLine.hasOption("v")) {
                isValidationOnly = true;
            }

            boolean isDebuggingEnable = false;
            if (oCommandLine.hasOption("d")) {
                isDebuggingEnable = true;
            }

            boolean isRunningInLocal = false;
            String strLocalDeviceContainerName = null;
            if (oCommandLine.hasOption("l")) {
                isRunningInLocal = true;
                strLocalDeviceContainerName = oCommandLine.getOptionValue("l");
            }

            boolean isInputModelValid;
            if (isRunningInLocal) {
                isInputModelValid = validateInputModel(true);
            } else {
                isInputModelValid = validateInputModel(false);
            }

            if (!isInputModelValid) {
                return;
            }

            if (isValidationOnly) {
                return;
            }

            if (!validateBBCoMSettings()) {
                return;
            }

            if (isRunningInLocal) {
                if (!runToolInLocal(strLocalDeviceContainerName)) {
                    return;
                }

                return; //don't use System.exit(0)
            }

            if (!runToolInGlobal(isDebuggingEnable)) {
                return;
            }
        } catch (Exception oException) {
            oException.printStackTrace();

            printInformation(oException.getMessage(), true, true);
            printUsageInformation(oOptions);
        }
    }

    /**
     * Run the tool in global
     *
     * @param isDebuggingEnable is debugging enable.
     * @return true if running the tool succeeded, false otherwise.
     */
    private boolean runToolInGlobal(boolean isDebuggingEnable) {
        try {
            BBCoMGlobalTraversing oBBCoMGlobalTraversing = new BBCoMGlobalTraversing(m_oBBCoMModeling, COMMAND_ARGS_TEMPLATE_IN_LOCAL, isDebuggingEnable);
            Map<String, String> oCommands = oBBCoMGlobalTraversing.traverse();

            printInformation("Run the following commands on the containers' machines:", true, true);
            for (Map.Entry<String, String> oCommand : oCommands.entrySet()) {
                printInformation("For '" + oCommand.getKey() + "': " + oCommand.getValue(), false, true);
            }

            return true;
        } catch (BBCoMTraversingException oBBCoMTraversingException) {
            printInformation(oBBCoMTraversingException.getMessage(), true, true);
            return false;
        }
    }

    /**
     * Run the tool in local.
     *
     * @return true if running the tool succeeded, false otherwise.
     */
    private boolean runToolInLocal(String localDeviceContainerName) {
        try {
            BBCoMLocalTraversing oBBCoMLocalTraversing = new BBCoMLocalTraversing(m_oBBCoMModeling, localDeviceContainerName);
            oBBCoMLocalTraversing.traverse();

            return true;
        } catch (BBCoMTraversingException oBBCoMTraversingException) {
            printInformation(oBBCoMTraversingException.getMessage(), true, true);
            return false;
        }

    }

    /**
     * Initialize the BBCoMModeling.
     *
     * @param isQuickValidation the BBCoMModeling should do a quick validation?.
     * @return the BBCoMModelingException if there is an error during the validation.
     */
    private BBCoMModelingException initializeBBCoMModeling(boolean isQuickValidation) {
        m_oBBCoMModeling = new BBCoMModeling();
        m_oBBCoMModeling.setQuickValidation(isQuickValidation);

        try {
            m_oBBCoMModeling.setBBCoMInstanceFilename(m_strInputModelFileName);

            return null;
        } catch (BBCoMModelingException oBBCoMModelingException) {
            return oBBCoMModelingException;
        }
    }

    /**
     * Get errors of an invalid BBCoMModeling.
     *
     * @param exception the exception caught from the BBCoMModeling.
     * @return the errors.
     */
    private StringBuilder getErrorsOfInvalidBBCoMModeling(BBCoMModelingException exception) {
        StringBuilder oReturnErrorContent = new StringBuilder();
        oReturnErrorContent.append(exception.getMessage()).append("\n");

        List<XmlError> oXMLErrors = m_oBBCoMModeling.getXMLErrors();

        if (oXMLErrors != null) {
            for (XmlError oXMLError : oXMLErrors) {
                oReturnErrorContent.append("Line ").append(oXMLError.getLine()).append(": ").append(oXMLError.getMessage()).append("\n");
            }
        }

        return oReturnErrorContent;
    }


    /**
     * Validate BBCoMSettings.
     *
     * @return true if BBCoMSettings is valid, false otherwise.
     */
    private boolean validateBBCoMSettings() {
        try {
            BBCoMSettings.getInstance();
            return true;
        } catch (Exception oException) {
            printInformation(oException.getMessage(), true, true);
            return false;
        }
    }


    /**
     * Validate the input model.
     *
     * @param isQuickValidation is a quick validation.
     * @return true if the input model is valid, otherwise false.
     * @throws Exception if there is an error.
     * @
     */
    private boolean validateInputModel(boolean isQuickValidation) throws Exception {
        printInformation("Validating the input model... ", true, false);

        BBCoMModelingException oCaughtException = initializeBBCoMModeling(isQuickValidation);

        printInformation("Done.", false, false);

        if (oCaughtException == null && m_oBBCoMModeling.isValid()) {
            printInformation("The input model is valid.", true, true);
            return true;
        }

        StringBuilder oErrorContent = getErrorsOfInvalidBBCoMModeling(oCaughtException);

        if (!isQuickValidation) {
            File oInputModelFile = new File(m_strInputModelFileName);
            String strErrorFileName = oInputModelFile.getName() + ".err";
            FileUtils.writeStringToFile(new File(oInputModelFile.getParent(), strErrorFileName), oErrorContent.toString());
            printInformation("The input model is not valid. Check the error file '" + strErrorFileName + "' for more details!", true, true);
            return false;
        }

        printInformation("The input model is not valid.\n" + oErrorContent.toString(), true, true);
        return false;
    }

    /**
     * Print the usage information.
     *
     * @param options the command line options.
     */
    private void printUsageInformation(Options options) {
        HelpFormatter oHelpFormatter = new HelpFormatter();

        oHelpFormatter.setWidth(80);
        oHelpFormatter.printHelp(USAGE, HEADER, options, FOOTER);
    }

    /**
     * Print information.
     *
     * @param information       information to print.
     * @param isOnNewLine       is printed on a new line.
     * @param isNewLinePrepared is a new line prepared.
     */
    private void printInformation(String information, boolean isOnNewLine, boolean isNewLinePrepared) {
        if (isOnNewLine) {
            System.out.println();
        }

        System.out.print(information);

        if (isNewLinePrepared) {
            System.out.println();
        }
    }
}
